// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ** This file is automatically generated by gapic-generator-typescript. **
// ** https://github.com/googleapis/gapic-generator-typescript **
// ** All changes to this file may be overwritten. **

import * as protos from '../protos/protos';
import * as assert from 'assert';
import * as sinon from 'sinon';
import {SinonStub} from 'sinon';
import {describe, it} from 'mocha';
import * as speechModule from '../src';

import {PassThrough} from 'stream';

import {protobuf, LROperation} from 'google-gax';

function generateSampleMessage<T extends object>(instance: T) {
  const filledObject = (instance.constructor as typeof protobuf.Message).toObject(
    instance as protobuf.Message<T>,
    {defaults: true}
  );
  return (instance.constructor as typeof protobuf.Message).fromObject(
    filledObject
  ) as T;
}

function stubSimpleCall<ResponseType>(response?: ResponseType, error?: Error) {
  return error
    ? sinon.stub().rejects(error)
    : sinon.stub().resolves([response]);
}

function stubSimpleCallWithCallback<ResponseType>(
  response?: ResponseType,
  error?: Error
) {
  return error
    ? sinon.stub().callsArgWith(2, error)
    : sinon.stub().callsArgWith(2, null, response);
}

function stubBidiStreamingCall<ResponseType>(
  response?: ResponseType,
  error?: Error
) {
  const transformStub = error
    ? sinon.stub().callsArgWith(2, error)
    : sinon.stub().callsArgWith(2, null, response);
  const mockStream = new PassThrough({
    objectMode: true,
    transform: transformStub,
  });
  return sinon.stub().returns(mockStream);
}

function stubLongRunningCall<ResponseType>(
  response?: ResponseType,
  callError?: Error,
  lroError?: Error
) {
  const innerStub = lroError
    ? sinon.stub().rejects(lroError)
    : sinon.stub().resolves([response]);
  const mockOperation = {
    promise: innerStub,
  };
  return callError
    ? sinon.stub().rejects(callError)
    : sinon.stub().resolves([mockOperation]);
}

function stubLongRunningCallWithCallback<ResponseType>(
  response?: ResponseType,
  callError?: Error,
  lroError?: Error
) {
  const innerStub = lroError
    ? sinon.stub().rejects(lroError)
    : sinon.stub().resolves([response]);
  const mockOperation = {
    promise: innerStub,
  };
  return callError
    ? sinon.stub().callsArgWith(2, callError)
    : sinon.stub().callsArgWith(2, null, mockOperation);
}

describe('v1p1beta1.SpeechClient', () => {
  it('has servicePath', () => {
    const servicePath = speechModule.v1p1beta1.SpeechClient.servicePath;
    assert(servicePath);
  });

  it('has apiEndpoint', () => {
    const apiEndpoint = speechModule.v1p1beta1.SpeechClient.apiEndpoint;
    assert(apiEndpoint);
  });

  it('has port', () => {
    const port = speechModule.v1p1beta1.SpeechClient.port;
    assert(port);
    assert(typeof port === 'number');
  });

  it('should create a client with no option', () => {
    const client = new speechModule.v1p1beta1.SpeechClient();
    assert(client);
  });

  it('should create a client with gRPC fallback', () => {
    const client = new speechModule.v1p1beta1.SpeechClient({
      fallback: true,
    });
    assert(client);
  });

  it('has initialize method and supports deferred initialization', async () => {
    const client = new speechModule.v1p1beta1.SpeechClient({
      credentials: {client_email: 'bogus', private_key: 'bogus'},
      projectId: 'bogus',
    });
    assert.strictEqual(client.speechStub, undefined);
    await client.initialize();
    assert(client.speechStub);
  });

  it('has close method', () => {
    const client = new speechModule.v1p1beta1.SpeechClient({
      credentials: {client_email: 'bogus', private_key: 'bogus'},
      projectId: 'bogus',
    });
    client.close();
  });

  it('has getProjectId method', async () => {
    const fakeProjectId = 'fake-project-id';
    const client = new speechModule.v1p1beta1.SpeechClient({
      credentials: {client_email: 'bogus', private_key: 'bogus'},
      projectId: 'bogus',
    });
    client.auth.getProjectId = sinon.stub().resolves(fakeProjectId);
    const result = await client.getProjectId();
    assert.strictEqual(result, fakeProjectId);
    assert((client.auth.getProjectId as SinonStub).calledWithExactly());
  });

  it('has getProjectId method with callback', async () => {
    const fakeProjectId = 'fake-project-id';
    const client = new speechModule.v1p1beta1.SpeechClient({
      credentials: {client_email: 'bogus', private_key: 'bogus'},
      projectId: 'bogus',
    });
    client.auth.getProjectId = sinon
      .stub()
      .callsArgWith(0, null, fakeProjectId);
    const promise = new Promise((resolve, reject) => {
      client.getProjectId((err?: Error | null, projectId?: string | null) => {
        if (err) {
          reject(err);
        } else {
          resolve(projectId);
        }
      });
    });
    const result = await promise;
    assert.strictEqual(result, fakeProjectId);
  });

  describe('recognize', () => {
    it('invokes recognize without error', async () => {
      const client = new speechModule.v1p1beta1.SpeechClient({
        credentials: {client_email: 'bogus', private_key: 'bogus'},
        projectId: 'bogus',
      });
      client.initialize();
      const request = generateSampleMessage(
        new protos.google.cloud.speech.v1p1beta1.RecognizeRequest()
      );
      const expectedOptions = {};
      const expectedResponse = generateSampleMessage(
        new protos.google.cloud.speech.v1p1beta1.RecognizeResponse()
      );
      client.innerApiCalls.recognize = stubSimpleCall(expectedResponse);
      const [response] = await client.recognize(request);
      assert.deepStrictEqual(response, expectedResponse);
      assert(
        (client.innerApiCalls.recognize as SinonStub)
          .getCall(0)
          .calledWith(request, expectedOptions, undefined)
      );
    });

    it('invokes recognize without error using callback', async () => {
      const client = new speechModule.v1p1beta1.SpeechClient({
        credentials: {client_email: 'bogus', private_key: 'bogus'},
        projectId: 'bogus',
      });
      client.initialize();
      const request = generateSampleMessage(
        new protos.google.cloud.speech.v1p1beta1.RecognizeRequest()
      );
      const expectedOptions = {};
      const expectedResponse = generateSampleMessage(
        new protos.google.cloud.speech.v1p1beta1.RecognizeResponse()
      );
      client.innerApiCalls.recognize = stubSimpleCallWithCallback(
        expectedResponse
      );
      const promise = new Promise((resolve, reject) => {
        client.recognize(
          request,
          (
            err?: Error | null,
            result?: protos.google.cloud.speech.v1p1beta1.IRecognizeResponse | null
          ) => {
            if (err) {
              reject(err);
            } else {
              resolve(result);
            }
          }
        );
      });
      const response = await promise;
      assert.deepStrictEqual(response, expectedResponse);
      assert(
        (client.innerApiCalls.recognize as SinonStub)
          .getCall(0)
          .calledWith(request, expectedOptions /*, callback defined above */)
      );
    });

    it('invokes recognize with error', async () => {
      const client = new speechModule.v1p1beta1.SpeechClient({
        credentials: {client_email: 'bogus', private_key: 'bogus'},
        projectId: 'bogus',
      });
      client.initialize();
      const request = generateSampleMessage(
        new protos.google.cloud.speech.v1p1beta1.RecognizeRequest()
      );
      const expectedOptions = {};
      const expectedError = new Error('expected');
      client.innerApiCalls.recognize = stubSimpleCall(undefined, expectedError);
      await assert.rejects(async () => {
        await client.recognize(request);
      }, expectedError);
      assert(
        (client.innerApiCalls.recognize as SinonStub)
          .getCall(0)
          .calledWith(request, expectedOptions, undefined)
      );
    });
  });

  describe('longRunningRecognize', () => {
    it('invokes longRunningRecognize without error', async () => {
      const client = new speechModule.v1p1beta1.SpeechClient({
        credentials: {client_email: 'bogus', private_key: 'bogus'},
        projectId: 'bogus',
      });
      client.initialize();
      const request = generateSampleMessage(
        new protos.google.cloud.speech.v1p1beta1.LongRunningRecognizeRequest()
      );
      const expectedOptions = {};
      const expectedResponse = generateSampleMessage(
        new protos.google.longrunning.Operation()
      );
      client.innerApiCalls.longRunningRecognize = stubLongRunningCall(
        expectedResponse
      );
      const [operation] = await client.longRunningRecognize(request);
      const [response] = await operation.promise();
      assert.deepStrictEqual(response, expectedResponse);
      assert(
        (client.innerApiCalls.longRunningRecognize as SinonStub)
          .getCall(0)
          .calledWith(request, expectedOptions, undefined)
      );
    });

    it('invokes longRunningRecognize without error using callback', async () => {
      const client = new speechModule.v1p1beta1.SpeechClient({
        credentials: {client_email: 'bogus', private_key: 'bogus'},
        projectId: 'bogus',
      });
      client.initialize();
      const request = generateSampleMessage(
        new protos.google.cloud.speech.v1p1beta1.LongRunningRecognizeRequest()
      );
      const expectedOptions = {};
      const expectedResponse = generateSampleMessage(
        new protos.google.longrunning.Operation()
      );
      client.innerApiCalls.longRunningRecognize = stubLongRunningCallWithCallback(
        expectedResponse
      );
      const promise = new Promise((resolve, reject) => {
        client.longRunningRecognize(
          request,
          (
            err?: Error | null,
            result?: LROperation<
              protos.google.cloud.speech.v1p1beta1.ILongRunningRecognizeResponse,
              protos.google.cloud.speech.v1p1beta1.ILongRunningRecognizeMetadata
            > | null
          ) => {
            if (err) {
              reject(err);
            } else {
              resolve(result);
            }
          }
        );
      });
      const operation = (await promise) as LROperation<
        protos.google.cloud.speech.v1p1beta1.ILongRunningRecognizeResponse,
        protos.google.cloud.speech.v1p1beta1.ILongRunningRecognizeMetadata
      >;
      const [response] = await operation.promise();
      assert.deepStrictEqual(response, expectedResponse);
      assert(
        (client.innerApiCalls.longRunningRecognize as SinonStub)
          .getCall(0)
          .calledWith(request, expectedOptions /*, callback defined above */)
      );
    });

    it('invokes longRunningRecognize with call error', async () => {
      const client = new speechModule.v1p1beta1.SpeechClient({
        credentials: {client_email: 'bogus', private_key: 'bogus'},
        projectId: 'bogus',
      });
      client.initialize();
      const request = generateSampleMessage(
        new protos.google.cloud.speech.v1p1beta1.LongRunningRecognizeRequest()
      );
      const expectedOptions = {};
      const expectedError = new Error('expected');
      client.innerApiCalls.longRunningRecognize = stubLongRunningCall(
        undefined,
        expectedError
      );
      await assert.rejects(async () => {
        await client.longRunningRecognize(request);
      }, expectedError);
      assert(
        (client.innerApiCalls.longRunningRecognize as SinonStub)
          .getCall(0)
          .calledWith(request, expectedOptions, undefined)
      );
    });

    it('invokes longRunningRecognize with LRO error', async () => {
      const client = new speechModule.v1p1beta1.SpeechClient({
        credentials: {client_email: 'bogus', private_key: 'bogus'},
        projectId: 'bogus',
      });
      client.initialize();
      const request = generateSampleMessage(
        new protos.google.cloud.speech.v1p1beta1.LongRunningRecognizeRequest()
      );
      const expectedOptions = {};
      const expectedError = new Error('expected');
      client.innerApiCalls.longRunningRecognize = stubLongRunningCall(
        undefined,
        undefined,
        expectedError
      );
      const [operation] = await client.longRunningRecognize(request);
      await assert.rejects(async () => {
        await operation.promise();
      }, expectedError);
      assert(
        (client.innerApiCalls.longRunningRecognize as SinonStub)
          .getCall(0)
          .calledWith(request, expectedOptions, undefined)
      );
    });
  });

  describe('streamingRecognize', () => {
    it('invokes streamingRecognize without error', async () => {
      const client = new speechModule.v1p1beta1.SpeechClient({
        credentials: {client_email: 'bogus', private_key: 'bogus'},
        projectId: 'bogus',
      });
      client.initialize();
      const request = generateSampleMessage(
        new protos.google.cloud.speech.v1p1beta1.StreamingRecognizeRequest()
      );
      const expectedResponse = generateSampleMessage(
        new protos.google.cloud.speech.v1p1beta1.StreamingRecognizeResponse()
      );
      client.innerApiCalls.streamingRecognize = stubBidiStreamingCall(
        expectedResponse
      );
      const stream = client._streamingRecognize();
      const promise = new Promise((resolve, reject) => {
        stream.on(
          'data',
          (
            response: protos.google.cloud.speech.v1p1beta1.StreamingRecognizeResponse
          ) => {
            resolve(response);
          }
        );
        stream.on('error', (err: Error) => {
          reject(err);
        });
        stream.write(request);
        stream.end();
      });
      const response = await promise;
      assert.deepStrictEqual(response, expectedResponse);
      assert(
        (client.innerApiCalls.streamingRecognize as SinonStub)
          .getCall(0)
          .calledWithExactly(undefined)
      );
      assert.deepStrictEqual(
        (((stream as unknown) as PassThrough)._transform as SinonStub).getCall(
          0
        ).args[0],
        request
      );
    });

    it('invokes streamingRecognize with error', async () => {
      const client = new speechModule.v1p1beta1.SpeechClient({
        credentials: {client_email: 'bogus', private_key: 'bogus'},
        projectId: 'bogus',
      });
      client.initialize();
      const request = generateSampleMessage(
        new protos.google.cloud.speech.v1p1beta1.StreamingRecognizeRequest()
      );
      const expectedError = new Error('expected');
      client.innerApiCalls.streamingRecognize = stubBidiStreamingCall(
        undefined,
        expectedError
      );
      const stream = client._streamingRecognize();
      const promise = new Promise((resolve, reject) => {
        stream.on(
          'data',
          (
            response: protos.google.cloud.speech.v1p1beta1.StreamingRecognizeResponse
          ) => {
            resolve(response);
          }
        );
        stream.on('error', (err: Error) => {
          reject(err);
        });
        stream.write(request);
        stream.end();
      });
      await assert.rejects(async () => {
        await promise;
      }, expectedError);
      assert(
        (client.innerApiCalls.streamingRecognize as SinonStub)
          .getCall(0)
          .calledWithExactly(undefined)
      );
      assert.deepStrictEqual(
        (((stream as unknown) as PassThrough)._transform as SinonStub).getCall(
          0
        ).args[0],
        request
      );
    });
  });
});
